﻿using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using Abp.Application.Services.Dto;
using Abp.Domain.Repositories;
using Abp.Extensions;
using Abp.Linq.Extensions;
using Abp.Threading;
using Abp.UI;
using DHJ.Cost.CostAccounting.Cache;
using DHJ.Cost.CostAccounting.Dto;
using DHJ.Cost.CostBooking.Config;
using DHJ.Cost.Models;
using DHJ.Cost.Tasks;
using DHJ.Cost.Tasks.Dto;
using Microsoft.EntityFrameworkCore;
using Task = DHJ.Cost.Tasks.Task;

namespace DHJ.Cost.CostAccounting
{
    public class BatchCostAppService : CostAppServiceBase, IBatchCostAppService
    {
        private readonly IBatchManager _batchManager;
        private readonly IBatchCostManager _batchCostManager;
        private readonly IRepository<Task, long> _taskRepository;
        private readonly IRepository<Pici, int> _piciRepository;
        private readonly IBatchCostCache _batchCostCache;

        public BatchCostAppService(IBatchManager batchManager, IBatchCostManager batchCostManager,
            IRepository<Task, long> taskRepository, IRepository<Pici, int> piciRepository, IBatchCostCache batchCostCache)
        {
            _batchManager = batchManager;
            _batchCostManager = batchCostManager;
            _taskRepository = taskRepository;
            _piciRepository = piciRepository;
            _batchCostCache = batchCostCache;
        }

        public async Task<ListResultDto<SimpleCostListDto>> GetSimpleCosts(BatchQueryDto batch)
        {
            var identifier = new BatchIdentifier(batch.Id, batch.Quantity);

            var costs = batch.IncludingChildren
                ? await GetSimpleCostsIncludingChildren(identifier)
                : await GetSelfSimpleCosts(identifier);

            return new ListResultDto<SimpleCostListDto>(costs);
        }

        public async Task<ListResultDto<SimpleCostListDto>> GetSimpleCostsByPici(int piciId)
        {
            var pici = await _piciRepository.GetAsync(piciId);

            if (pici == null) throw new UserFriendlyException("查询批次出错");

            var taskPrefixes = new List<string>();

            if (!pici.TaskPrefixes.IsNullOrWhiteSpace())
            {
                taskPrefixes.AddRange(pici.TaskPrefixes.Split(','));
            }

            var tasks = await _taskRepository.GetAll().Where(e => taskPrefixes.Any(s => e.TaskNum.StartsWith(s)))
                .ToListAsync();

            var batches = await _batchManager.Batches().Include(e => e.Task).Where(e => tasks.Any(t => t.Id == e.TaskId))
                .Where(e => e.Quantity > 0)
                .Select(e => new BatchIdentifier(e.Id, e.Quantity)).ToListAsync();

            var items = await _batchCostCache.GetCacheItems();

            if (items == null || items.Count == 0)
            {
                return new ListResultDto<SimpleCostListDto>();
            }

            var newItems = items.Where(e => batches.Contains(new BatchIdentifier(e.BatchId, e.Quantity))).ToList();

            //var costs = new List<SimpleCostListDto>();

            var result = newItems.GroupBy(e => e.Category).Select(g => new SimpleCostListDto()
            {
                Category = g.Key,
                Amount = g.Sum(e => e.Cost) / pici.Quantity
            }).ToList();


            return new ListResultDto<SimpleCostListDto>(result);
        }

        public async Task<ListResultDto<SimpleCostListDto>> GetSimpleCostsByTask(long id)
        {
            var task = await _taskRepository.FirstOrDefaultAsync(id);

            var batches = await _batchManager.Batches().Where(e => task.Id == e.TaskId)
                .Where(e => e.Quantity > 0)
                .Select(e => new BatchIdentifier(e.Id, e.Quantity)).ToListAsync();

            var costs = new List<SimpleCostListDto>();

            foreach (var batch in batches)
            {
                costs.AddRange(await GetSelfSimpleCosts(batch));
            }

            var result = costs.GroupBy(c => c.Category).Select(g => new SimpleCostListDto
            {
                Category = g.Key,
                Amount = g.Sum(c => c.Amount)
            }).ToList();

            return new ListResultDto<SimpleCostListDto>(result);
        }

        public async Task<PagedResultDto<CostDetailListDto>> GetCostDetails(GetCostDetailInput input)
        {
            var identifier = new BatchIdentifier(input.Batch.Id, input.Batch.Quantity);

            var query = input.Batch.IncludingChildren
                ? GetCostDetailsIncludingChildren(identifier)
                : GetSelfCostDetails(identifier);

            var count = await query.CountAsync();
            var costs = await query
                .OrderBy(input.Sorting)
                .PageBy(input)
                .ToListAsync();

            return new PagedResultDto<CostDetailListDto>
            {
                Items = costs,
                TotalCount = count
            };
        }

        public async Task<PagedResultDto<CostDetailListDto>> GetCostDetailsByPici(GetCostDetailByPiciInput input)
        {

            var pici = await _piciRepository.GetAsync(input.PiciId);

            if (pici == null) throw new UserFriendlyException("查询批次出错");

            var taskPrefixes = new List<string>();

            if (!pici.TaskPrefixes.IsNullOrWhiteSpace())
            {
                taskPrefixes.AddRange(pici.TaskPrefixes.Split(','));
            }

            var tasks = await _taskRepository.GetAll().Where(e => taskPrefixes.Any(s => e.TaskNum.StartsWith(s)))
                .ToListAsync();

            var batches = await _batchManager.Batches().Where(e => tasks.Any(t => t.Id == e.TaskId))
                          .Where(e => e.Quantity > 0)
                .Select(e => new BatchIdentifier(e.Id, e.Quantity)).ToListAsync();


            var items = (await _batchCostCache.GetDetailCacheItems()).Select(e => new CostDetailListDto()
            {

                BatchId = e.BatchId,
                Quantity = e.Quantity,
                Amount = e.Amount,
                AssemQuantity = e.AssemQuantity,
                Category = e.Category,
                Ratio =e.Ratio,
                Time = e.Time,
                Title = e.Title,
                UnitCost =e.UnitCost,
                Workshop = e.Workshop
            }).ToList();

            var newItems = items.Where(e => batches.Contains(new BatchIdentifier(e.BatchId, e.Quantity))).ToList();


            var count = newItems.Count;
            var costs = newItems
                .Skip(input.SkipCount)
                .Take(input.MaxResultCount)
                .ToList();

            return new PagedResultDto<CostDetailListDto>
            {
                Items = costs,
                TotalCount = count
            };
        }

        public async Task<PagedResultDto<CostDetailListDto>> GetCostDetailsByTask(GetCostDetailByTaskInput input)
        {
            var task = await _taskRepository.FirstOrDefaultAsync(input.TaskId);

            var batches = await _batchManager.Batches().Where(e => task.Id == e.TaskId)
                .Where(e => e.Quantity > 0)
                .Select(e => new BatchIdentifier(e.Id, e.Quantity)).ToListAsync();

            var batch = batches.FirstOrDefault();

            if (batch == null)
                return new PagedResultDto<CostDetailListDto>
                {
                    Items = new List<CostDetailListDto>(),
                    TotalCount = 0
                };
            var query = GetSelfCostDetails(batch);

            for (var i = 0; i < batches.Count; i++)
            {
                if (i != 0)
                    query = query.Concat(GetSelfCostDetails(batches[i]));
            }

            var count = query.Count();
            var costs = query
                //.OrderBy(input.Sorting)
                .PageBy(input)
                .ToList();

            return new PagedResultDto<CostDetailListDto>
            {
                Items = costs,
                TotalCount = count
            };
        }

        public async Task<GetBatchesCostsOutput> GetBatchesCosts(GetBatchesCostsInput input)
        {
            var cols = CostConfigProvider.GetAllCostTypes();

            var query = (from cost in _batchCostManager.BatchCosts.Include(e => e.ActualCost)
                         join batch in _batchManager.Batches().Include(e => e.Task)
                         on cost.BatchId equals batch.Id into batches
                         from batch in batches.DefaultIfEmpty()
                         select new { cost, batch })
                        .Where(e => e.cost.ActualCost.Time.Month == input.Month)
                        .Where(e => e.cost.ActualCost.Time.Year == input.Year)
                        .Where(e => e.cost.Workshop == input.Workshop)
                        .WhereIf(!input.Filter.IsNullOrWhiteSpace(),
                                e => e.cost.BatchId.Contains(input.Filter) ||
                                e.batch.Task.TaskNum.Contains(input.Filter) ||
                                e.batch.Task.ProductName.Contains(input.Filter) ||
                                e.batch.Task.ProductId.Contains(input.Filter))
                        .GroupBy(p => new { p.cost.BatchId, p.cost.Quantity });


            var totalCount = await query.CountAsync();
            var groups = await query.PageBy(input).ToListAsync();

            var batchesCosts = new List<BatchCostsDto>();
            foreach (var group in groups)
            {
                var batchCostsDto = new BatchCostsDto { Id = group.Key.BatchId, Quantity = group.Key.Quantity};

                var costs = new List<decimal?>();
                foreach (var col in cols)
                {
                    var items = group.Where(e =>
                            e.cost.ActualCost.Detail.Category == col.Category && e.cost.ActualCost.Detail.Title == col.Title)
                        .ToList();
                    if (!items.Any())
                    {
                        costs.Add(null);
                    }
                    else
                    {
                        costs.Add(items.Sum(a => a.cost.UnitCost * group.Key.Quantity));
                    }
                }

                var task = group.FirstOrDefault()?.batch?.Task;

                if (task != null)
                {
                    batchCostsDto.TaskNum = task.TaskNum;
                    batchCostsDto.ProductId = task.ProductId;
                    batchCostsDto.ProductName = task.ProductName;
                }

                batchCostsDto.Costs = costs;
                batchesCosts.Add(batchCostsDto);
            }

            return new GetBatchesCostsOutput(totalCount, batchesCosts, cols);
        }

        private async Task<List<SimpleCostListDto>> GetSelfSimpleCosts(IBatchIdentifier batch)
        {
            //var costs = _batchCostManager.GetCosts(batch);
            var costs = await _batchCostManager.GetCostListAsync(batch);

            var result = costs.GroupBy(a => a.ActualCost.Detail.Category).Select(g => new SimpleCostListDto
            {
                Category = g.Key,
                Amount = g.Sum(a => a.UnitCost * batch.Quantity)
            }).ToList();

            return result;
        }

        private async Task<List<SimpleCostListDto>> GetSimpleCostsIncludingChildren(IBatchIdentifier batch)
        {
            var assemRels = await _batchManager.GetFlatAssemRelsAsync(batch);

            var costs = new List<SimpleCostListDto>();
            foreach (var assemRel in assemRels)
            {
                costs.AddRange(await GetSelfSimpleCosts(new BatchIdentifier(assemRel.PartId, assemRel.PartQuantity)));
            }

            var result = costs.GroupBy(s => s.Category).Select(g => new SimpleCostListDto
            {
                Category = g.Key,
                Amount = g.Sum(s => s.Amount)
            });

            return result.ToList();
        }

        private IQueryable<CostDetailListDto> GetSelfCostDetails(IBatchIdentifier batch)
        {
            return _batchCostManager.GetCosts(batch).Select(e => new CostDetailListDto
            {
                BatchId = e.BatchId,
                Quantity = e.Quantity,
                Workshop = e.Workshop,
                Ratio = e.Ratio,
                Time = e.ActualCost.Time,
                Category = e.ActualCost.Detail.Category,
                Title = e.ActualCost.Detail.Title,
                Amount = e.ActualCost.Detail.Amount,
                UnitCost = e.ActualCost.Detail.Amount * e.Ratio / e.Quantity,
                AssemQuantity = batch.Quantity
            });
        }

        private IQueryable<CostDetailListDto> GetCostDetailsIncludingChildren(IBatchIdentifier batch)
        {
            var assemRels = AsyncHelper.RunSync(() => _batchManager.GetFlatAssemRelsAsync(batch));

            Debug.Assert(assemRels.Count >= 1);
            var costs = GetSelfCostDetails(new BatchIdentifier(assemRels[0].PartId, assemRels[0].PartQuantity));
            for (var i = 1; i < assemRels.Count; i++)
                costs = costs.Concat(
                    GetSelfCostDetails(new BatchIdentifier(assemRels[i].PartId, assemRels[i].PartQuantity)));

            return costs;
        }
    }
}